import type { Request as IRequest, Response as IResponse, NextFunction as INextFunction, ErrorRequestHandler, RequestHandler } from 'express';
import { DependencyContainer, container } from 'tsyringe'
import { Request, Response, NextFunction, ROOT, Injector, Body, Query, Headers, WEBSOCKET, WEBSOCKETS, WechatConfig, SERVER_CALL } from "./tokens";
import { Router } from "./Router";
import { CoreError } from "./error";

export const getErrorsMsg = (error: Error) => {
    const errors: any[] = Reflect.get(error, 'errors') || [];
    const msgs: string[] = [];
    errors.map(err => {
        console.error(error)
        const { code, expected, received, message, path } = err;
        msgs.push(`${message}(${code}) ${(path || []).join('.')} expected ${expected} but received ${received}`)
    })
    return msgs.join('\n')
}
export const w7Error: ErrorRequestHandler = (error: Error, req: IRequest, res: IResponse, next: INextFunction) => {
    console.log({ error })
    if (error) {
        if (error instanceof CoreError) {
            res.status(error.code).json(error.toJson())
            return;
        }
        if (Reflect.has(error, 'errors')) {
            const messages = getErrorsMsg(error)
            res.json({ success: false, message: messages, errors: Reflect.get(error, 'errors') || [] })
            return;
        }
        if (Reflect.has(error, 'toJson')) {
            res.json((error as any).toJson())
            return;
        }
        const code = Reflect.get(error, 'code')
        res.json({ success: false, message: error.message, data: { code: code, stack: error.stack } });
    } else {
        next();
    }
}
import { WebSocket } from 'ws'
import { IncomingMessage } from 'http'
import { getFilePath, parseScope } from './parse';
import { existsSync } from 'fs-extra';
import { skerReq } from './module';
import { dirname, join } from 'path';
import { wechatConfig } from './wechatConfig';
export interface SocketHandler {
    (socket: WebSocket, req: IncomingMessage, injector: DependencyContainer): void;
}
export const socketCache: Map<string, WebSocket> = new Map();
container.register(WEBSOCKETS, { useValue: socketCache })
export const w7Socket: SocketHandler = async (socket, req, injector) => {
    const childContainer = injector.createChildContainer()
    childContainer.register(WEBSOCKET, { useValue: socket })
    childContainer.register(SERVER_CALL, { useValue: false })
    const root = container.resolve(ROOT)
    const url = new URL(req.url, 'http://localhost')
    const paths = url.pathname.split('/')
    const [start, ...others] = paths;
    const query = injector.resolve(Query)
    const queryObj: any = {}
    url.searchParams.forEach((value, key, p) => {
        Reflect.set(queryObj, key, value)
    });
    /**
     * socket 唯一标识
     */
    const cacheKey = queryObj.uuid;
    if (cacheKey) {
        const old = socketCache.get(cacheKey)
        if (old) {
            old.close()
        }
        Reflect.set(socket, 'uuid', cacheKey)
        socketCache.set(cacheKey, socket);
    }
    socket.on('close', () => {
        socketCache.delete(cacheKey);
    });
    socket.on('error', console.error)
    socket.on('pong', ()=>{
        Reflect.set(socket, 'isAlive', true)
    });
    const { scope, addon, platform, controller, action, d } = parseScope(others, req.headers, query);
    const filePath = getFilePath({ root, scope, addon, platform, controller, action, d, method: 'ws' })
    if (filePath && existsSync(filePath)) {
        let pkg = skerReq([dirname(filePath)], childContainer)(filePath)
        if (typeof pkg === 'undefined') {
            socket.close()
            return;
        }
        const method = Reflect.get(pkg, d)
        if (method) {
            if (typeof method === 'function') {
                method({ type: 'connection', payload: queryObj, injector: childContainer })
                socket.on('message', async (message, isBinary) => {
                    const msg = message.toString('utf-8')
                    let paylaod = msg;
                    try {
                        paylaod = JSON.parse(msg)
                        await method({ type: 'message', paylaod, injector: childContainer })
                    } catch (e) {
                        socket.send(JSON.stringify({ type: 'error', message: (e as any).message }))
                    }
                })
            } else {
                pkg = method;
            }
        }
        const _action = Reflect.get(pkg, action)
        if (_action) {
            if (typeof _action === 'function') {
                _action({ type: 'connection', payload: queryObj, injector: childContainer })
                socket.on('message', async (message, isBinary) => {
                    const msg = message.toString('utf-8')
                    let paylaod = msg;
                    try {
                        paylaod = JSON.parse(msg)
                        await _action({ type: 'message', paylaod, injector: childContainer })
                    } catch (e) {
                        socket.send(JSON.stringify({ type: 'error', message: (e as any).message }))
                    }
                })
            } else {
                pkg = action;
            }
        }
        const _controller = Reflect.get(pkg, controller)
        if (_controller) {
            if (typeof _controller === 'function') {
                _controller({ type: 'connection', payload: queryObj, injector: childContainer })
                socket.on('message', async (message, isBinary) => {
                    const msg = message.toString('utf-8')
                    let paylaod = msg;
                    try {
                        paylaod = JSON.parse(msg)
                        await _controller({ type: 'message', paylaod, injector: childContainer })
                    } catch (e) {
                        socket.send(JSON.stringify({ type: 'error', message: (e as any).message }))
                    }
                })
            } else {
                pkg = controller;
            }
        }
        if (Reflect.has(pkg, `ws`)) {
            pkg.ws({ type: 'connection', payload: queryObj, injector: childContainer })
            socket.on('message', async (message, isBinary) => {
                const msg = message.toString('utf-8')
                let paylaod = msg;
                try {
                    paylaod = JSON.parse(msg)
                    await pkg.ws({ type: 'message', paylaod, injector: childContainer })
                } catch (e) {
                    socket.send(JSON.stringify({ type: 'error', message: (e as any).message }))
                }
            })
            return
        }
        if (Reflect.has(pkg, 'default')) {
            pkg.default({ type: 'connection', payload: queryObj, injector: childContainer })
            socket.on('message', async (message, isBinary) => {
                const msg = message.toString('utf-8')
                let paylaod = msg;
                try {
                    paylaod = JSON.parse(msg)
                    await pkg.default({ type: 'message', paylaod, injector: childContainer })
                } catch (e) {
                    socket.send(JSON.stringify({ type: 'error', message: (e as any).message }))
                }
            })
            return
        }
        socket.close(404, 'not found')
        return;
    }
}
export const w7: RequestHandler = async (req: IRequest, res: IResponse, next: INextFunction) => {
    const now = Date.now();
    const childContainer = container.createChildContainer()
    childContainer.register(SERVER_CALL, { useValue: false })
    childContainer.register(Request, { useValue: req })
    childContainer.register(Response, { useValue: res })
    childContainer.register(NextFunction, { useValue: next })
    childContainer.register(Injector, { useValue: childContainer })
    childContainer.register(Body, { useValue: req.body })
    childContainer.register(Query, { useValue: req.query })
    childContainer.register(Headers, { useValue: req.headers })
    childContainer.register(WechatConfig, { useValue: () => wechatConfig(req.headers) })
    res.setTimeout(60 * 60 * 1000)
    res.on('close', () => {
        childContainer.dispose()
    })
    res.on('finish', () => {
        childContainer.dispose()
    })
    res.on('error', (err: Error) => {
        console.error(err)
    })
    const router = new Router(childContainer)
    Reflect.set(req, 'router', router)
    childContainer.register(Router, { useValue: router })
    try {
        const result = await router.load();
        if (typeof result === 'undefined') {
            if (res.headersSent) return;
            return next();
        }
        if (typeof result === 'object') {
            res.set('time', `${(Date.now() - now) / 1000}s`).json(result)
            return;
        }
        if (['string', 'number', 'boolean'].includes(typeof result)) {
            res.set('time', `${(Date.now() - now) / 1000}s`).send(`${result}`).end()
            return;
        }
    } catch (e) {
        return next(e)
    }
}
